#include "Server.h"
#include <QTimerEvent>

Server::Server(QObject *parent)
{
    //有客户端连接到服务器发送newConnection信号，链接处理的槽函数
    connect(this, &QTcpServer::newConnection, this, &Server::slotNewConnection);

    //服务器每2秒检查一遍每个客户端是否心跳超时，超时就离线踢除
    mTimerCheckHeartbeat = startTimer(2000);           //启动定时器事件，每隔2s返回一个定时器id

    //服务器每3秒通知一次各客户端房间人数状态
    mTimerUpdateRoomState = startTimer(3000);          //启动定时器事件，每隔3s返回一个定时器id
}

bool Server::Start(int port) //服务器开启
{
    return listen(QHostAddress::Any, port);  //监听
}

void Server::slotNewConnection() //newConnection的槽函数
{
    if (hasPendingConnections()) { //有客户端连接到服务器
        QTcpSocket* socket = nextPendingConnection();                    //创建套接字
        if (socket->isValid()) {                                         //socket己经实例化
            Player *p = new Player(socket);                              //创建玩家
            connect(p, &Player::sigRecvMsg, this, &Server::slotRecvMsg); //玩家的接收消息信号，连接服务器接收消息槽函数
        }
    }
}

void Server::slotRecvMsg(char *pData, int nDataLen) //接收消息槽函数
{
    Player* p = (Player*)sender();                            //创建玩家 为信号发送者（玩家）
    MSG_HEADER* pHeader = (MSG_HEADER*)pData;                 //创建消息头（char*转成MSG_HEADER）
    OnMsg(p, pHeader, (char*)(pHeader + 1), nDataLen);        //处理接收到的玩家TCP消息
}


void Server::Log(QString text) //发送日志信号的函数
{
    emit sigLog(text);   //发送日志信号
}

bool Server::Add( Player* p) //新增玩家
{
    QMutexLocker locker(&mMutex); //互斥锁
    mPlayers.append(p);           //玩家列表里增添这个玩家
    return true;
}

bool Server::Remove(const QString name)   //删除玩家
{
    QMutexLocker locker(&mMutex);
    foreach (Player* p, mPlayers) { //遍历玩家列表
        if(p->getPlayermName() == name) {      //名字相同
            mPlayers.removeOne(p); //玩家列表里删除玩家
            return true;
        }
    }
    return true;
}

bool Server::Remove(Player* p) //删除玩家
{
    QMutexLocker locker(&mMutex);
    mPlayers.removeOne(p);        //玩家列表里删除玩家
    return true;
}

Player* Server::Find(const QString name)  //查询玩家
{
    QMutexLocker locker(&mMutex);
    foreach (Player* p, mPlayers) {  //遍历玩家列表
        if(p->getPlayermName() == name) {      //名字相同
            return p;
        }
    }
    return nullptr;
}

int Server::CountPlayersInRoom(int room) //统计某个房间内的玩家数
{
    QMutexLocker locker(&mMutex);
    int nCount = 0;                //初始为0
    foreach (Player* p, mPlayers) { //遍历玩家列表
        if(p->getPlayermRoom() == room) {      //有玩家的房间号为本房间号
            nCount++;
        }
    }
    return nCount;
}


void Server::CountPlayersInRoom(int room, ROOM_STATE& s) //统计某个房间内的玩家数，给传入的房间状态类型的对象赋值（人数和玩家名字）
{
    QMutexLocker locker(&mMutex);

    QList<Player*> players;          //创建个玩家列表（局部的）
    int nCount = 0;
    foreach(Player* p, mPlayers) {               //遍历玩家列表（成员的）
        if (p->getPlayermRoom() == room) {       //有玩家的房间号为本房间号
            players.append(p);                   //玩家列表（局部的）添加这个玩家
            nCount++;
        }
    }

    for (int i=0;i<players.size();i++) {                //遍历玩家列表（局部的）
        Player* p = players.at(i);                      //取玩家
        if (i == 0) {
            strcpy(s.szName1, p->getPlayermName().toLocal8Bit());  //赋值客户端1 name
        }
        else if (i == 1) {
            strcpy(s.szName2, p->getPlayermName().toLocal8Bit());  //赋值客户端2 name
        }
    }
    s.nPlayers = nCount;                                //赋值房间人数
}

void Server::CheckHeartbeat()  //检查心跳
{
    QMutexLocker locker(&mMutex);
    foreach(Player* p, mPlayers) {                                          //遍历玩家列表
        if (p->IsHeartbeatTimeout()) {                                      //玩家心跳超时了
            Log(QStringLiteral("客户端(%1)连接超时，断开连接").arg(p->getPlayermName())); //发送日志信号的函数

            QString name = p->getPlayermName();                     //取心跳超时玩家名字
            int room = p->getPlayermRoom();                         //取心跳超时玩家的房间号
            mPlayers.removeOne(p);                       //删除玩家

            foreach(Player* pp, mPlayers) {                   //再次遍历玩家列表
                if (pp->getPlayermRoom() == room) {                     //刚删除玩家所在的房间有玩家
                    //给同一房间所有人发该玩家掉线消息
                    MSG_HEADER msg = { 0 };                 //创建消息头
                    msg.nType = MA_OFFLINE;                 //消息类型断开连接
                    strcpy(msg.szName, name.toLocal8Bit()); //客户端name 心跳超时的玩家名字
                    p->Send(msg);                           //心跳超时的玩家发送消息头
                }
            }
        }
    }
}

void Server::UpdateRoomState() //向所有在线玩家发送当前所有房间人数状态
{
    //所有房间玩家状态
    states.clear();                         //房间状态列表
    for (int i = 0; i < maxRoom; i++) {       //遍历全部房间号
        ROOM_STATE s;                       //创建房间状态
        CountPlayersInRoom(i, s);            //统计房间号为i的房间内的玩家数，给s赋值（人数和玩家名字）
        states.append(s);                   //房间状态列表添加房间号为i的房间状态
    }

    int nLen = maxRoom * sizeof(ROOM_STATE);       //房间数*房间状态对象的大小
    char* buf = new char[nLen];                    //创建指定长度的char*数组
    memset(buf, 0, nLen);                          //数组初始赋值0

    for (int i = 0; i < states.size(); i++) {                          //遍历房间状态列表
        ROOM_STATE s = states.at(i);                                  //取房间号为i的房间的状态
        memcpy(buf + i * sizeof(ROOM_STATE), &s, sizeof(ROOM_STATE)); //按下标，逐个把房间的状态存到buf
    }

    QMutexLocker locker(&mMutex);
    foreach (Player* p, mPlayers) {            //遍历玩家列表
        MSG_HEADER header = { 0 };            //创建文件头
        header.nType = MA_UPDATEROOMSTATE;    //消息类型 服务器更新房间信息
        header.nDataLen = nLen;               //本MSG_HEADER 之后接的额外数据的长度
        p->Send(header, buf, nLen);           //给玩家发复杂TCP消息，含消息头和附加nLen字节的数据
    }

    delete []buf;             //delete char*数组
}

void Server::timerEvent(QTimerEvent *e) //重写定时器事件
{
    if (mTimerCheckHeartbeat == e->timerId()) {
        CheckHeartbeat();  //检查心跳
    }
    else if (mTimerUpdateRoomState == e->timerId()) {
        UpdateRoomState(); //向所有在线玩家发送当前所有房间人数状态
    }
}

void Server::OnMsg(Player* p, MSG_HEADER* pHeader, char *pData, int nDataLen) //处理接收到的玩家TCP消息
{
    switch (pHeader->nType) {
    case MQ_LOGON:			//客户端登录
    {
        OnMsgLogon(p, pHeader);
        break;
    }
    case MQ_MOVE:			//客户端五子棋走棋
    {
        OnMsgMove(p, pHeader);
        break;
    }
    case MQ_MOVECC:			//客户端象棋走棋
    {
        OnMsgMoveCC(p, pHeader);
        break;
    }
    case MQ_SUCCESS:		//客户端胜利
    {
        OnMsgSuccess(p, pHeader);
        break;
    }
    case MQ_SAY:            //客户端说话
    {
        OnMsgSay(p, pHeader);
        break;
    }
    case MQ_HEARTBEAT:
    {
        OnMsgHeartBeat(p, pHeader);  //客户端发来心跳
        break;
    }
    default:
    {
        break;
    }
    }
}

void Server::OnMsgLogon(Player* player, MSG_HEADER* pHeader) //客户端玩家申请登陆
{
    QString name = QString::fromLocal8Bit(pHeader->szName);                      //取客户端name
    int room = pHeader->nNum1;                                                   //取房间号
    Log(QStringLiteral("客户端(%1)请求登陆：房间%2").arg(name).arg(room));          //发送日志信号的函数

    int nCount = CountPlayersInRoom(room);                                       //统计某个房间内的玩家数
    if (nCount >= 2) {                                                            //房间人数>=2
        Log(QStringLiteral("客户端(%1)登陆失败！房间%2已满！").arg(name).arg(room)); //发送日志信号的函数

        MSG_HEADER msg = { 0 };                                                  //创建消息头
        msg.nType = MA_LOGON;                                                    //消息类型 登录回复
        msg.nNum1 = 0;                                                           //应答：登录成功nNum1=1/登录失败nNum1=0
        player->Send(msg);                                                       //给玩家发简单TCP消息，只有消息头即可
    }
    else {                                                                        //房间人数<2
        if (Find(name)) {                                                         //查询玩家，名字为本客户端，查到了
            Log(QStringLiteral("客户端(%1)登陆失败！用户已存在！").arg(name));       //发送日志信号的函数
            return;
        }
        Log(QStringLiteral("客户端(%1)登陆成功！房间%2").arg(name).arg(room));      //没查到，发送日志信号的函数

        player->UpdateHeartbeat();           //玩家更新心跳
        player->setPlayermName(name);        //玩家名字赋值
        player->setPlayermRoom(room);        //玩家房间号赋值
        Add(player);                         //新增玩家
        nCount++;                            //该房间的玩家数+1

        MSG_HEADER msg = {0};        //创建消息头
        msg.nType = MA_LOGON;        //消息类型 回复登录
        msg.nNum1 = 1;               //应答：登录成功nNum1=1/登录失败nNum1=0
        msg.nNum3 = nCount;          //当前房间人数
        strcpy(msg.szName, pHeader->szName); //客户端name

        bool bFirst = true;                                  //是不是第一个玩家标志
        foreach(Player* p, mPlayers) {                       //遍历玩家列表
            if (p->getPlayermRoom() == room) {               //该房间的玩家，每次先遍历到的一定是第一个人
                //给同一房间所有人发登陆消息
                p->Send(msg);                                //给玩家发简单TCP消息，只有消息头即可

                //如果该房间用户达到2个，给同一房间所有人发开始消息
                //并指定第一个玩家为黑棋，之后为白棋
                if (nCount >= 2) {                           //第二个玩家进入时，触发if
                    if (bFirst) {                             //是第一个玩家
                        p->setPlayermBlack(true);            //赋黑棋
                        bFirst = false;                      //标志设为false，第二个玩家成功登录本房间就会触发else白棋
                    }
                    else {
                        p->setPlayermBlack(false);                   //白棋
                    }
                    MSG_HEADER header2 = { 0 };              //创建消息头
                    header2.nType = MA_START;                //消息类型 对局开始
                    header2.nNum2 = p->getPlayermBlack() ? 1 : 0;       //黑棋传1，白棋传0
                    p->Send(header2);                        //给玩家发简单TCP消息，只有消息头即可
                }
            }
        }
    }
}

void Server::OnMsgMove(Player* player, MSG_HEADER* pHeader) //客户端走棋
{
    int x = pHeader->nNum1;  //从消息头获取走棋坐标
    int y = pHeader->nNum2;

    QString strMsg = QString(QStringLiteral("客户端(%1)走棋：(%2,%3)")).arg(player->getPlayermName()).arg(x).arg(y); //格式化
    Log(strMsg);             //发送日志信号的函数

    //给同一个房间的其他玩家发走棋消息
    foreach(Player* p, mPlayers) {                              //遍历玩家列表
        if (p->getPlayermRoom() == player->getPlayermRoom() && p != player) {         //本房间号的其他玩家
            MSG_HEADER msg = {0};                             //创建消息头
            msg.nType = MA_MOVE;                               //消息类型 服务端下发走一步数据
            msg.nNum1 = x;                                     //传走棋坐标
            msg.nNum2 = y;
            strcpy(msg.szName, player->getPlayermName().toLocal8Bit());   //客户端name 玩家名字
            p->Send(msg);                                      //给玩家发简单TCP消息，只有消息头即可
        }
    }
}

void Server::OnMsgMoveCC(Player *player, MSG_HEADER *pHeader)
{
    int x1 = pHeader->nNum1;  //从消息头获取走棋坐标
    int y1 = pHeader->nNum2;
    int x2 = pHeader->nNum3;
    int y2 = pHeader->nNum4;

    QString strMsg = QString(QStringLiteral("客户端(%1)走棋：(%2,%3)到(%4,%5)"))
                         .arg(player->getPlayermName()).arg(x1).arg(y1).arg(x2).arg(y2); //格式化
    Log(strMsg);             //发送日志信号的函数

    //给同一个房间的其他玩家发走棋消息
    foreach(Player* p, mPlayers) {                              //遍历玩家列表
        if (p->getPlayermRoom() == player->getPlayermRoom() && p != player) {         //本房间号的其他玩家
            MSG_HEADER msg = {0};                               //创建消息头
            msg.nType = MA_MOVECC;                              //消息类型 服务端下发走一步数据
            msg.nNum1 = x1;                                     //传走棋坐标
            msg.nNum2 = y1;
            msg.nNum3 = x2;
            msg.nNum4 = y2;
            strcpy(msg.szName, player->getPlayermName().toLocal8Bit());   //客户端name 玩家名字
            if (p->Send(msg)) {                                           //给玩家发简单TCP消息，只有消息头即可
                //qDebug() << "p->Send(msg)"; //调试日志
            }
        }
    }
}

void Server::OnMsgSuccess(Player* player, MSG_HEADER* pHeader) //客户端胜利
{
    QString strMsg = QString(QStringLiteral("客户端(%1)胜利")).arg(player->getPlayermName());  //格式化
    Log(strMsg);     //发送日志信号的函数
}

void Server::OnMsgSay(Player* player, MSG_HEADER *pHeader) //客户端说话
{
    QString text = QString::fromLocal8Bit(pHeader->szSay);                                       //消息头的客户端说话内容
    QString strMsg = QString(QStringLiteral("客户端(%1)说：%2")).arg(player->getPlayermName()).arg(text);    //格式化
    Log(strMsg);                         //发送日志信号的函数

    //给同一个房间的所有用户发送说话消息
    foreach(Player* p, mPlayers) {        //遍历玩家列表
        if (p->getPlayermRoom() == player->getPlayermRoom()) {  //本房间号的玩家
            pHeader->nType = MA_SAY;     //消息类型 玩家说话（原来是MQ_SAY）
            p->Send(*pHeader);           //给玩家发简单TCP消息，只有消息头即可
        }
    }
}

void Server::OnMsgHeartBeat(Player* player, MSG_HEADER *pHeader) //客户端心跳
{
    player->UpdateHeartbeat(); //玩家更新心跳
}
